package glin

import (
	"embed"
	"errors"
	"fmt"
	"html/template"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
)

// Delims represents a set of Left and Right delimiters for HTML template rendering.
type Delims struct {
	// Left delimiter, defaults to {{.
	Left string
	// Right delimiter, defaults to }}.
	Right string
}

// 菜单的回调函数
type TemplateMenuCallBack func(c Context, name string) string

// 设置权限的回调
type TemplateAuthCallBack func(c Context, name string) bool

// 设置多语言翻译
type TemplateLangCallBack func(c Context, content string) string

// 模板解析的内容
type TemplateContent struct {
	LayoutUrl string //layout地址
	Content   string //模板内容
}

// 自定义标签元素
type TemplateTag struct {
	ElementName string //元素
	AttrName    string //属性
	Content     string //解析的内容
	AttrValue   string //属性内容
}
type ContextCallBack func(c Context) map[string]any

// 自己实现的
type RendererTemplate struct {
	Mu             sync.RWMutex
	delims         Delims
	funcMap        template.FuncMap           //自定义模板函数
	defaultFuncMap template.FuncMap           //内部自定义模板函数
	IsDebug        bool                       //是否调试模式---每次实时加载数
	TemplateList   map[string]TemplateContent //所有的模板内容
	ViewPath       string                     //如果是本地文件路径
	fs             embed.FS                   //直接加载嵌入的文件系统
	extension      string                     //模板文件后缀,带.
	setMenu        TemplateMenuCallBack       //设置菜单的回调函数
	setAuth        TemplateAuthCallBack       //设置是否有权限
	setLang        TemplateLangCallBack       //得到内容，返回对应的字符串
	globalContext  ContextCallBack            //设置全局的变量,主要是解决显示账户
}

// 设置模板引擎{{ }},默认是{{}}
func (r *RendererTemplate) SetDelims(left, right string) {
	r.delims.Left = left
	r.delims.Right = right
}

// 设置全局的函数
func (r *RendererTemplate) SetFuncMap(funcMap template.FuncMap) {
	r.funcMap = funcMap
}

// 设置文件后缀
func (r *RendererTemplate) SetExtension(extension string) {
	r.extension = extension
}

// 加载所有的模板
func (r *RendererTemplate) LoadTemplate() {
	if r.extension == "" {
		r.extension = ".html"
	}
	r.defaultFuncMap = template.FuncMap{
		"checkMenu": func(c Context, url string) template.HTML {
			if r.setMenu != nil {
				return template.HTML(r.setMenu(c, url))
			}
			return template.HTML("")
		}, //{{menu .glin url}}
		"checkAuth": func(c Context, url string) bool {
			if r.setAuth == nil {
				return true
			}
			return r.setAuth(c, url)
		}, //{{if auth .glin url }}内容 {{end}}
		"checkLang": func(c Context, content string) string {
			if r.setLang == nil {
				return content
			}
			return r.setLang(c, content)
		}, //{{lang .glin content }}
		"checkUrl": func(c Context, url string) string { //得到当前的路径
			//url:支持带参数和/:
			path := c.Path()
			_url := url
			isPath := false
			for _, v := range c.ParamNames() {
				vv := c.Param(v)
				if strings.Index(_url, ":"+v) < 0 {
					continue
				}
				isPath = true
				url = strings.ReplaceAll(url, ":"+v, vv)
			}

			param := ""
			if strings.LastIndex(url, "?") > 0 {
				param = url[strings.LastIndex(url, "?")+1:]
				url = url[0:strings.LastIndex(url, "?")]
			} else {
				for k, v := range c.QueryParams() {
					for _, v1 := range v {
						param += "&" + k + "=" + v1
					}
				}
				if param != "" {
					param = param[1:]
				}
			}

			if param != "" {
				param = "?" + param
			}

			if url == "" { //空值就得到当前的路径
				for _, v := range c.ParamNames() {
					vv := c.Param(v)
					path = strings.ReplaceAll(path, ":"+v, vv)
				}
				return path + param
			}
			p0 := url[0:1]
			if p0 == "/" { //绝对地址，只需要添加后面的参数
				return url + param
			}
			if len(c.ParamNames()) == 0 { //不存在/
				arr0 := strings.Split(path, "/") //存在
				arr1 := strings.Split(url, "/")  //存在多个/就替换多个
				returnurl := ""
				for i := 0; i < len(arr0)-len(arr1); i++ {
					returnurl += "/" + arr0[i]
				}
				returnurl += "/" + url
				if returnurl[0:2] == "//" {
					returnurl = returnurl[1:]
				}
				return returnurl + param
			}
			//说明存在:/
			pparam := ""
			if !isPath { //已经有参数，不能再替换
				for _, v := range c.ParamNames() {
					vv := c.Param(v)
					pparam += "/" + vv
				}
			}
			purl := path[0 : strings.Index(path, ":")-1]
			arr0 := strings.Split(purl, "/") //存在
			arr1 := strings.Split(url, "/")  //存在多个/就替换多个
			returnurl := ""
			for i := 0; i < len(arr0)-len(arr1); i++ {
				returnurl += "/" + arr0[i]
			}

			returnurl += "/" + url
			if returnurl[0:2] == "//" {
				returnurl = returnurl[1:]
			}
			rr := returnurl + pparam + param

			return rr

		},
	}
	r.TemplateList = make(map[string]TemplateContent)
	r.autoLoadTemplate()
}

// 模板名称统一处理
func (r *RendererTemplate) normalizeName(name string) string {
	if name == "" {
		return ""
	}
	// 确保以/开头
	if name[0] != '/' {
		name = "/" + name
	}
	// 去除扩展名
	if strings.HasSuffix(name, r.extension) {
		name = name[:len(name)-len(r.extension)]
	}
	return strings.ToLower(filepath.ToSlash(name))
}

// 直接读取本地文件
func (r *RendererTemplate) readLocalFile(name string) (string, error) {
	name += r.extension
	var content []byte
	var err error
	if r.ViewPath == "" {
		if name[0] == '/' {
			name = name[1:]
		}
		content, err = r.fs.ReadFile(name)
	} else {
		content, err = os.ReadFile(r.ViewPath + name)
	}
	if err != nil {
		return "", err
	}
	return string(content), nil
}

// 加载本地文件
func (r *RendererTemplate) loadLocalFile() (err error) {
	r.Mu.Lock()
	defer r.Mu.Unlock()
	_, err = os.ReadDir(r.ViewPath)
	if err != nil {
		return
	}
	err = filepath.WalkDir(r.ViewPath, func(path string, d os.DirEntry, err error) error {
		if d.IsDir() {
			return nil
		}
		if filepath.Ext(d.Name()) != r.extension {
			return nil
		}
		content, err := os.ReadFile(path)
		if err != nil {
			return nil
		}
		_rel, _ := filepath.Rel(r.ViewPath, path)
		tempName := _rel[:len(_rel)-len(r.extension)] //filepath.Base(path)
		tempName = "/" + strings.ToLower(tempName)
		tempName = filepath.ToSlash(tempName)
		r.TemplateList[tempName] = r.parseHtml(string(content), tempName)
		return nil
	})
	return
}

// 加载fs文件系统模板
func (r *RendererTemplate) loadEmbedFile() (err error) {
	r.Mu.Lock()
	defer r.Mu.Unlock()
	err = fs.WalkDir(r.fs, ".", func(path string, d fs.DirEntry, err error) error {
		if path == "." {
			return nil
		}
		if d.IsDir() {
			return nil
		}
		if filepath.Ext(d.Name()) != r.extension {
			return nil
		}
		content, err := r.fs.ReadFile(path)
		if err != nil {
			return nil
		}
		tempName := path[0 : len(path)-len(r.extension)] //filepath.Base(path)
		tempName = "/" + strings.ToLower(tempName)
		tempName = strings.ReplaceAll(tempName, "\\", "/")
		r.TemplateList[tempName] = r.parseHtml(string(content), tempName)
		return nil
	})
	return err
}

// 提高效率，在设置模板的时候，自动加载模板文件到内存中，提高效率
func (r *RendererTemplate) autoLoadTemplate() {
	if r.ViewPath == "" {
		r.loadEmbedFile()
	} else {
		r.loadLocalFile()
	}
}

// 开始解析
func (r *RendererTemplate) parseHtml(html, route string) (content TemplateContent) {
	content.LayoutUrl, html = r.parseLayoutTag(html, "layout", "url")
	html = r.parseMenuTag(html, "menu", "url", `{{checkMenu .glin "%s" }}`)
	html = r.parseAuthTag(html, "auth", "url", `{{if checkAuth .glin "%s" }} %s {{end}}`)
	html = r.parseLangTag(html, "lang", `{{checkLang .glin "%s" }}`)
	html = r.parseLangTag(html, "url", `{{checkUrl .glin "%s" }}`)
	content.Content = html
	return
}

// 设置菜单回调函数
func (r *RendererTemplate) SetMenuCallback(callback TemplateMenuCallBack) {
	r.setMenu = callback
}

// 设置权限回调函数
func (r *RendererTemplate) SetAuthCallback(callback TemplateAuthCallBack) {
	r.setAuth = callback
}

// 设置多语言回调函数
func (r *RendererTemplate) SetLangCallback(callback TemplateLangCallBack) {
	r.setLang = callback
}

// 设置本地的模板路径,viewPath:是相对路径
func (r *RendererTemplate) SetViewPath(viewPath string) {
	r.ViewPath = viewPath
}

// 设置fs的文件路径
func (r *RendererTemplate) SetFsPath(fs embed.FS) {
	r.fs = fs
}

// 设置全局的变量
func (r *RendererTemplate) SetGlobalContext(val ContextCallBack) {
	r.globalContext = val
}

// 解析自定义的标签
func (r *RendererTemplate) parseMenuTag(html, elementName, attrName, replaceContent string) (content string) {
	// 简化的正则表达式，匹配 <block url="/menu/index"> </block> 格式
	pattern := fmt.Sprintf(`<%s\s+%s\s*=\s*["']([^"']*)["'][^>]*>([\s\S]*?)</%s>`, elementName, attrName, elementName)
	re := regexp.MustCompile(pattern)

	// 使用 FindAllStringSubmatch 获取所有匹配内容
	matches := re.FindAllStringSubmatch(html, -1)
	if len(matches) == 0 {
		return html
	}

	// 从后往前替换，避免位置偏移问题
	result := html
	for i := len(matches) - 1; i >= 0; i-- {
		match := matches[i]
		if len(match) < 3 {
			continue
		}
		// match[0] 是整个匹配的字符串
		// match[1] 是属性值
		// match[2] 是标签内容
		originalTag := match[0]
		attrVal := strings.TrimSpace(match[1])
		//tagContent := match[2]
		// 使用 replaceContent 格式化替换内容
		// replaceContent 应该包含 %s 占位符来接收属性值
		replacement := fmt.Sprintf(replaceContent, attrVal)
		//fmt.Printf("  替换为: %s\n", replacement)
		// 替换整个标签
		result = strings.Replace(result, originalTag, replacement, 1)
	}

	content = result
	return
}

// 解析自定义的标签
func (r *RendererTemplate) parseAuthTag(html, elementName, attrName, replaceContent string) (content string) {
	// 简化的正则表达式，匹配 <block url="/menu/index"> </block> 格式
	pattern := fmt.Sprintf(`<%s\s+%s\s*=\s*["']([^"']*)["'][^>]*>([\s\S]*?)</%s>`, elementName, attrName, elementName)
	re := regexp.MustCompile(pattern)

	// 使用 FindAllStringSubmatch 获取所有匹配内容
	matches := re.FindAllStringSubmatch(html, -1)
	if len(matches) == 0 {
		return html
	}

	// 从后往前替换，避免位置偏移问题
	result := html
	for i := len(matches) - 1; i >= 0; i-- {
		match := matches[i]
		if len(match) < 3 {
			continue
		}
		// match[0] 是整个匹配的字符串
		// match[1] 是属性值
		// match[2] 是标签内容
		originalTag := match[0]
		attrVal := strings.TrimSpace(match[1])
		tagContent := match[2]
		// 使用 replaceContent 格式化替换内容
		// replaceContent 应该包含 %s 占位符来接收属性值
		replacement := fmt.Sprintf(replaceContent, attrVal, tagContent)
		//fmt.Printf("  替换为: %s\n", replacement)
		// 替换整个标签
		result = strings.Replace(result, originalTag, replacement, 1)
	}

	content = result
	return
}

// 解析layout是否存在,如果存在，就返回url
func (r *RendererTemplate) parseLayoutTag(html, elementName, attrName string) (layout, content string) {

	// 简化的正则表达式，匹配 <block url="/menu/index"> </block> 格式
	pattern := fmt.Sprintf(`<%s\s+%s\s*=\s*["']([^"']*)["'][^>]*>([\s\S]*?)</%s>`, elementName, attrName, elementName)
	re := regexp.MustCompile(pattern)

	// 使用 FindAllStringSubmatch 获取所有匹配内容
	matches := re.FindAllStringSubmatch(html, -1)
	if len(matches) == 0 { //不存在layout布局--直接返回html
		content = html
		return
	}

	// 从后往前替换，避免位置偏移问题
	result := html
	for i := len(matches) - 1; i >= 0; i-- {
		match := matches[i]
		if len(match) < 3 {
			continue
		}
		// match[0] 是整个匹配的字符串
		// match[1] 是属性值
		// match[2] 是标签内容
		originalTag := match[0]
		layout = strings.TrimSpace(match[1])
		// 使用 replaceContent 格式化替换内容
		// replaceContent 应该包含 %s 占位符来接收属性值
		replacement := ""
		//fmt.Printf("  替换为: %s\n", replacement)
		// 替换整个标签
		result = strings.Replace(result, originalTag, replacement, 1)
	}
	content = result
	return
}

// 解析lang，没有name
func (r *RendererTemplate) parseLangTag(html, elementName, replaceContent string) (content string) {
	pattern := fmt.Sprintf(`<\s*%s\s*>([\s\S]*?)<\s*/\s*%s\s*>`, elementName, elementName)
	re := regexp.MustCompile(pattern)

	// 使用 FindAllStringSubmatch 获取所有匹配内容
	matches := re.FindAllStringSubmatch(html, -1)
	if len(matches) == 0 {
		return html
	}

	// 从后往前替换，避免位置偏移问题
	result := html
	for i := len(matches) - 1; i >= 0; i-- {
		match := matches[i]
		if len(match) < 2 {
			continue
		}
		// match[0] 是整个匹配的字符串
		// match[1] 是属性值
		// match[2] 是标签内容
		originalTag := match[0]
		tagContent := match[1]
		// 使用 replaceContent 格式化替换内容
		// replaceContent 应该包含 %s 占位符来接收属性值
		replacement := fmt.Sprintf(replaceContent, tagContent)
		//fmt.Printf("  替换为: %s\n", replacement)
		// 替换整个标签
		result = strings.Replace(result, originalTag, replacement, 1)
	}

	content = result
	return
}

// 得到模板内容
func (r *RendererTemplate) getTplContent(name string) (content TemplateContent, err error) {
	r.Mu.Lock()
	defer r.Mu.Unlock()
	content, ok := r.TemplateList[name]
	if ok {
		return
	}
	err = errors.New("模板不存在:" + name)
	return
}

// 输出对象，模板名称(不包含后缀)，数据，glin请求对象
func (r *RendererTemplate) Render(out io.Writer, name string, data interface{}, c Context) (err error) {
	param := make(map[string]interface{})
	param["glin"] = c
	if r.globalContext != nil {
		globalContent := r.globalContext(c)
		for k, v := range globalContent {
			param[k] = v
		}
	}
	if data != nil {
		for k, v := range data.(map[string]interface{}) {
			param[k] = v
		}
	}
	tempFuncMap := template.FuncMap{}
	contentTpl, err := r.getTplContent(name)
	if err != nil {
		return
	}
	if r.defaultFuncMap != nil {
		for k, v := range r.defaultFuncMap {
			tempFuncMap[k] = v
		}
	}
	if r.funcMap != nil {
		for k, v := range r.funcMap {
			tempFuncMap[k] = v
		}
	}

	if r.delims.Left == "" {
		r.delims.Left = "{{"
	}
	if r.delims.Right == "" {
		r.delims.Right = "}}"
	}

	//没有用到layout布局
	if contentTpl.LayoutUrl == "" {
		outTpl := template.New(name).Delims(r.delims.Left, r.delims.Right).Funcs(tempFuncMap)
		outTpl, err = outTpl.Parse(contentTpl.Content)
		if err != nil {
			return err
		}
		err = outTpl.Execute(out, param)
		return
	}
	//得到layout内容
	layout, err := r.getTplContent(contentTpl.LayoutUrl)
	if err != nil {
		return
	}
	outTpl := template.New("").Delims(r.delims.Left, r.delims.Right).Funcs(tempFuncMap)
	outTpl, err = outTpl.Parse(layout.Content)
	if err != nil {
		return
	}
	outTpl, err = outTpl.Parse(contentTpl.Content)
	if err != nil {
		return err
	}
	err = outTpl.Execute(out, param)
	//读取模板的内容
	//先解析layout，如果有，需要先解析对应的block的位置
	//在解析主模板内容
	//最后执行一个模板
	return
}
